

/*
以下接口并未按照时序
    reg[14:0] V;    (原是329开始变化,其实是328~329之间).现在变为328变化


时序处理
    at_bitL8  329开始变化,337为下一个


有关oam的计算
    一个主要的OAM sram      大小为0x100, 一共64个精灵
    一个辅助的OAM sram      大小为32,    一共8个精灵 (用于处理下一行扫描线)
        OAM内存结构为 [Y] [Pat] [Att] [X]   []对应每一个字节
        扫描线水平计数会从1~64   清除OAM sram的数据(填充$ff)
        扫描线水平计数从65~256   计算OAM的Y坐标数据
        扫描线水平计数冲261~320  读取OAM的tile数据到缓存内

    以下是当前渲染扫描线的数据.(经过辅助OAM sram处理后生成的)
        oam_tileL[0:7]      8个对应精灵的低位tile的8位数据      总共64位
        oam_tileH[0:7]      8个对应精灵的高位tile的8位数据      总共64位
        oam_pos[0:7]        8个对应精灵的8位坐标数据(0~255).    相当于8个0~256计数器.每绘制一个像素,则减一
        oam_pal0[0:7]       8个对应精灵的1位调色板数据低位
        oam_pal1[0:7]       8个对应精灵的1位调色板数据高位
        oam_att0[0:7]       8个对应精灵的属性
        oam_att1[0:7]       8个对应精灵的属性
        oam_prio[0:7]       8个对应精灵的优先级

    有关oam_pos设置从X=261开始.(相当于(X&0x7)==5) 依次为(261,269,277,285,293,301,309,317)

有部分是根据https://www.qmtpro.com/~nes/chipimages/visual2c02/ 时序实现
    有关sprite的实现.未参考visual2c02
    有关BG的实现,大部分的时序对应实现.(但实现并不是,只是简单的模拟像素时序.)
        visual2c02是以21MHz的频率实现,有关ppu_dat的引脚数据.可能是在半个像素的时候读取到指定寄存器
            有关PPU_ALE.则可能是半个像素后拉低(更实际的情况是在持续读的时候,高电平为100ns.低电平持续280ns).然后ppu_dat作为高阻态 (ALE在拉高的时候.ppu_dat指向的是ppu_addr[7:0])
        有关CVBS的输出.则以晶振的上升沿与下降沿更新数据.则为42MHz
            实际上42MHz分12个像素方波输出. 则3.56MHz(这个是CVBS的副载波频率)
            这12个像素其实对应的是调色板低4位的'h00~'h0d. 而调色板的高2位则是输出电平的高低位(其实就是亮度)
        有关CVBS的副载波.其实相当于如下(使用类似方波的形式实现模拟相位)
        v  :HLHLHLHLHLHL        晶振电平
        c  :123456789012        索引
            111111------        当color = 'h1
            22222------2        当color = 'h2
            3333------33        ..
            444------444        ..
            55------5555        ..
            6------66666        ..
            ------777777        ..
            -----888888-        ..
            ----999999--        ..
            ---AAAAAA---        ..
            --BBBBBB----        ..
            -CCCCCC-----        当color = 'hc
        color[5:4]只是对应亮度值(也就是高低电平的不同). 如上 - 表示低电平,  1,2,3,4表示高电平状态
            三个像素分别是 'h1,'h2,'h3  则时序如下
            111111------22222------23333------33        对应上图.但实际上有问题
            111111------22222------*3333------**        去掉一些像素时序模糊问题
            HHHHHHLLLLLLHHHHHLLLLLLHHHHHLLLLLLHH        实际上输出可能是这样子(需要修正)
        虽然最有效的是应该使用12个时钟实现像素相位.但NES的像素更新却是以8位更新.
            相当于晶振执行了12次像素处理.NES像素时钟确是以8个时钟更新
            |012345678901           CVBS像素显示相位时钟
            |111111------|
            |------------|
            |pix0    |pix2...       NES的像素读取时钟. (需要把数字平均)
            |111111-----*22222-     实际是像这样渲染.*表示可忽略
            |111111--****55----     渲染1,5像素时
                更详细的说明应该参考visual2c02的CVBS输出



有关task的说明
    task_ppu_rst:       重置按钮
    task_ppu_ctrl:      处理写入ppu的控制器
    task_ppu_mask:      处理写入ppu的mask
    task_ppu_statues    读取ppu的状态
    task_ppu_oamaddr    设置ppu的oam地址
    task_ppu_oamdata    写入oam数据.oam地址递增
    task_ppu_scroll     写入T寄存器.卷轴设置
    task_ppu_addr       写入T寄存器.设置ppu的数据写入位置
    task_ppu_data       通过ppu写入sram数据


*/

`define PPU_CTRL        'h00
`define PPU_MASK        'h01
`define PPU_STATUES     'h02
`define PPU_OAMADDR     'h03
`define PPU_OAMDATA     'h04
`define PPU_SCROLL      'h05
`define PPU_ADDR        'h06
`define PPU_DATA        'h07

`define PPU_V_END       'd261
`define PPU_H_END       'd340

module ppu (
    //ppu接口
    ppu_addr,
    ppu_dat,
    ppu_clk,
    ppu_rw,
    ppu_rd,
    ppu_cs,
    ppu_nmi,

    //cpu接口
    cpu_addr,
    cpu_dat,
    cpu_rw,

    //输出像素
    out_pixel,
    out_x,
    out_y,

    rstn,

    T,
);
    //addr
    output reg[13:0] ppu_addr = 'd0; /*synthesis keep=1 */
    inout  wire[7:0]  ppu_dat; assign ppu_dat =
        (~ppu_rw)?ppu_out_dat:         //如果rw==0.则输出数据
        (~ppu_rd)?8'hzz:8'hzz          //rd==0或者rd==1都为高阻态
    ;
    input  wire ppu_clk;                //以5.8MHz
    output reg  ppu_rw = 1'b1;
    output reg  ppu_rd = 1'b1;
    output reg  ppu_nmi = 1'b1;
    input wire ppu_cs;      reg ppu_cs_old = 'd1;

    //cpu
    input wire[2:0] cpu_addr;
    //如果cpu_rw==1,cs==0,则输出数据
    inout wire[7:0] cpu_dat; /*synthesis keep=1 */
    reg [7:0] cpu_out_dat = 'd0; assign cpu_dat = (cpu_rw&~ppu_cs_old)?cpu_out_dat:8'hzz;
    input wire cpu_rw;

    output wire[5:0] out_pixel;     //0~63
    output wire[8:0] out_x; assign out_x = hcounter;
    output wire[8:0] out_y; assign out_y = vcounter;
    input wire rstn;

    ///////////////////////////////////////////////////////////////////////////
    //定义ppu内部寄存器
    reg [15:0] V = 16'd0;
    output reg [15:0] T = 16'd0;
    reg w = 1'd0;
    reg [2:0] fine_x = 'd0;

    reg [7:0] ppu_ctrl = 8'h10;         /*synthesis keep=1 */
    reg [7:0] ppu_mask = 8'h1f;

    reg [8:0] hcounter = `PPU_V_END;    /*synthesis keep=1 */
    reg [8:0] vcounter = 'd260;         /*synthesis keep=1 */

    //oam寄存器
    reg [7:0] oam_addr = 'd0;
    reg [7:0] oam['hff:'h00];     initial begin /* $readmemh("ppu_oam.hex",oam);*/ end

    //读写寄存器
    reg [7:0] ppu_out_dat = 'd0;
    reg [7:0] last_ppu_dat = 'd0; /*synthesis keep=1 */
    reg [5:0] pal_ram['h1f:'h00];
    reg tile_shift = 'd0;
    //状态寄存器
    reg vblank = 'd0;
    wire [7:0] ppu_status = {vblank,sprite_0_hit,sprite_line_over,5'd2};

//`include "pal_ram.v"
`define INIT_PAL \
        pal_ram['h00] <= 'h22;pal_ram['h01] <= 'h29;pal_ram['h02] <= 'h1A;pal_ram['h03] <= 'h0F;pal_ram['h04] <= 'h22;pal_ram['h05] <= 'h36;pal_ram['h06] <= 'h17;pal_ram['h07] <= 'h0F;pal_ram['h08] <= 'h22;pal_ram['h09] <= 'h30;pal_ram['h0a] <= 'h21;pal_ram['h0b] <= 'h0F;pal_ram['h0c] <= 'h22;pal_ram['h0d] <= 'h07;pal_ram['h0e] <= 'h17;pal_ram['h0f] <= 'h0F;pal_ram['h10] <= 'h22;pal_ram['h11] <= 'h16;pal_ram['h12] <= 'h27;pal_ram['h13] <= 'h18;pal_ram['h14] <= 'h0F;pal_ram['h15] <= 'h1A;pal_ram['h16] <= 'h30;pal_ram['h17] <= 'h27;pal_ram['h18] <= 'h0F;pal_ram['h19] <= 'h16;pal_ram['h1a] <= 'h30;pal_ram['h1b] <= 'h27;pal_ram['h1c] <= 'h0F;pal_ram['h1d] <= 'h0F;pal_ram['h1e] <= 'h36;pal_ram['h1f] <= 'h17;

`define INIT_OAM \
        oam['h0] <= 'h18;oam['h1] <= 'hFF;oam['h2] <= 'h23;oam['h3] <= 'h58;oam['h4] <= 'hB0;oam['h5] <= 'hFC;oam['h6] <= 'h00;oam['h7] <= 'h28;oam['h8] <= 'hB0;oam['h9] <= 'hFC;oam['hA] <= 'h00;oam['hB] <= 'h30;oam['hC] <= 'hB8;oam['hD] <= 'hFC;oam['hE] <= 'h00;oam['hF] <= 'h28;oam['h10] <= 'hB8;oam['h11] <= 'hFC;oam['h12] <= 'h00;oam['h13] <= 'h30;oam['h14] <= 'hC0;oam['h15] <= 'h3A;oam['h16] <= 'h00;oam['h17] <= 'h28;oam['h18] <= 'hC0;oam['h19] <= 'h37;oam['h1A] <= 'h00;oam['h1B] <= 'h30;oam['h1C] <= 'hC8;oam['h1D] <= 'h4F;oam['h1E] <= 'h00;oam['h1F] <= 'h28;oam['h20] <= 'hC8;oam['h21] <= 'h4F;oam['h22] <= 'h40;oam['h23] <= 'h30;oam['h24] <= 'hF8;oam['h25] <= 'h00;oam['h26] <= 'h00;oam['h27] <= 'h00;oam['h28] <= 'hF8;oam['h29] <= 'h00;oam['h2A] <= 'h00;oam['h2B] <= 'h00;oam['h2C] <= 'hF8;oam['h2D] <= 'h00;oam['h2E] <= 'h00;oam['h2F] <= 'h00;oam['h30] <= 'hF8;oam['h31] <= 'h00;oam['h32] <= 'h00;oam['h33] <= 'h00;oam['h34] <= 'hF8;oam['h35] <= 'h00;oam['h36] <= 'h00;oam['h37] <= 'h00;oam['h38] <= 'hF8;oam['h39] <= 'h00;oam['h3A] <= 'h00;oam['h3B] <= 'h00;oam['h3C] <= 'hF8;oam['h3D] <= 'h00;oam['h3E] <= 'h00;oam['h3F] <= 'h00;oam['h40] <= 'hF8;oam['h41] <= 'h00;oam['h42] <= 'h00;oam['h43] <= 'h00;oam['h44] <= 'hF8;oam['h45] <= 'h00;oam['h46] <= 'h00;oam['h47] <= 'h00;oam['h48] <= 'hF8;oam['h49] <= 'h00;oam['h4A] <= 'h00;oam['h4B] <= 'h00;oam['h4C] <= 'hF8;oam['h4D] <= 'h00;oam['h4E] <= 'h00;oam['h4F] <= 'h00;oam['h50] <= 'hF8;oam['h51] <= 'h00;oam['h52] <= 'h00;oam['h53] <= 'h00;oam['h54] <= 'hF8;oam['h55] <= 'h00;oam['h56] <= 'h00;oam['h57] <= 'h00;oam['h58] <= 'hF8;oam['h59] <= 'h00;oam['h5A] <= 'h00;oam['h5B] <= 'h00;oam['h5C] <= 'hF8;oam['h5D] <= 'h00;oam['h5E] <= 'h00;oam['h5F] <= 'h00;oam['h60] <= 'hF8;oam['h61] <= 'h00;oam['h62] <= 'h00;oam['h63] <= 'h00;oam['h64] <= 'hF8;oam['h65] <= 'h00;oam['h66] <= 'h00;oam['h67] <= 'h00;oam['h68] <= 'hF8;oam['h69] <= 'h00;oam['h6A] <= 'h00;oam['h6B] <= 'h00;oam['h6C] <= 'hF8;oam['h6D] <= 'h00;oam['h6E] <= 'h00;oam['h6F] <= 'h00;oam['h70] <= 'hF8;oam['h71] <= 'h00;oam['h72] <= 'h00;oam['h73] <= 'h00;oam['h74] <= 'hF8;oam['h75] <= 'h00;oam['h76] <= 'h00;oam['h77] <= 'h00;oam['h78] <= 'hF8;oam['h79] <= 'h00;oam['h7A] <= 'h00;oam['h7B] <= 'h00;oam['h7C] <= 'hF8;oam['h7D] <= 'h00;oam['h7E] <= 'h00;oam['h7F] <= 'h00;oam['h80] <= 'hF8;oam['h81] <= 'h00;oam['h82] <= 'h00;oam['h83] <= 'h00;oam['h84] <= 'hF8;oam['h85] <= 'h00;oam['h86] <= 'h00;oam['h87] <= 'h00;oam['h88] <= 'hF8;oam['h89] <= 'h00;oam['h8A] <= 'h00;oam['h8B] <= 'h00;oam['h8C] <= 'hF8;oam['h8D] <= 'h00;oam['h8E] <= 'h00;oam['h8F] <= 'h00;oam['h90] <= 'hF8;oam['h91] <= 'h00;oam['h92] <= 'h00;oam['h93] <= 'h00;oam['h94] <= 'hF8;oam['h95] <= 'h00;oam['h96] <= 'h00;oam['h97] <= 'h00;oam['h98] <= 'hF8;oam['h99] <= 'h00;oam['h9A] <= 'h00;oam['h9B] <= 'h00;oam['h9C] <= 'hF8;oam['h9D] <= 'h00;oam['h9E] <= 'h00;oam['h9F] <= 'h00;oam['hA0] <= 'hF8;oam['hA1] <= 'h00;oam['hA2] <= 'h00;oam['hA3] <= 'h00;oam['hA4] <= 'hF8;oam['hA5] <= 'h00;oam['hA6] <= 'h00;oam['hA7] <= 'h00;oam['hA8] <= 'hF8;oam['hA9] <= 'h00;oam['hAA] <= 'h00;oam['hAB] <= 'h00;oam['hAC] <= 'hF8;oam['hAD] <= 'h00;oam['hAE] <= 'h00;oam['hAF] <= 'h00;oam['hB0] <= 'hF8;oam['hB1] <= 'h00;oam['hB2] <= 'h00;oam['hB3] <= 'h00;oam['hB4] <= 'hF8;oam['hB5] <= 'h00;oam['hB6] <= 'h00;oam['hB7] <= 'h00;oam['hB8] <= 'hF8;oam['hB9] <= 'h00;oam['hBA] <= 'h00;oam['hBB] <= 'h00;oam['hBC] <= 'hF8;oam['hBD] <= 'h00;oam['hBE] <= 'h00;oam['hBF] <= 'h00;oam['hC0] <= 'hF8;oam['hC1] <= 'h00;oam['hC2] <= 'h00;oam['hC3] <= 'h00;oam['hC4] <= 'hF8;oam['hC5] <= 'h00;oam['hC6] <= 'h00;oam['hC7] <= 'h00;oam['hC8] <= 'hF8;oam['hC9] <= 'h00;oam['hCA] <= 'h00;oam['hCB] <= 'h00;oam['hCC] <= 'hF8;oam['hCD] <= 'h00;oam['hCE] <= 'h00;oam['hCF] <= 'h00;oam['hD0] <= 'hF8;oam['hD1] <= 'h00;oam['hD2] <= 'h00;oam['hD3] <= 'h00;oam['hD4] <= 'hF8;oam['hD5] <= 'h00;oam['hD6] <= 'h00;oam['hD7] <= 'h00;oam['hD8] <= 'hF8;oam['hD9] <= 'h00;oam['hDA] <= 'h00;oam['hDB] <= 'h00;oam['hDC] <= 'hF8;oam['hDD] <= 'h00;oam['hDE] <= 'h00;oam['hDF] <= 'h00;oam['hE0] <= 'hF8;oam['hE1] <= 'h00;oam['hE2] <= 'h00;oam['hE3] <= 'h00;oam['hE4] <= 'hF8;oam['hE5] <= 'h00;oam['hE6] <= 'h00;oam['hE7] <= 'h00;oam['hE8] <= 'hF8;oam['hE9] <= 'h00;oam['hEA] <= 'h00;oam['hEB] <= 'h00;oam['hEC] <= 'hF8;oam['hED] <= 'h00;oam['hEE] <= 'h00;oam['hEF] <= 'h00;oam['hF0] <= 'hF8;oam['hF1] <= 'h00;oam['hF2] <= 'h00;oam['hF3] <= 'h00;oam['hF4] <= 'hF8;oam['hF5] <= 'h00;oam['hF6] <= 'h00;oam['hF7] <= 'h00;oam['hF8] <= 'hF8;oam['hF9] <= 'h00;oam['hFA] <= 'h00;oam['hFB] <= 'h00;oam['hFC] <= 'hF8;oam['hFD] <= 'h00;oam['hFE] <= 'h00;oam['hFF] <= 'h00;

    assign out_pixel = pal_ram[mix_color];
    wire spr_nocolor = sprite_color[1:0]==2'd0;
    wire [4:0]  mix_color =  spr_nocolor?{1'b0,color}:{1'b1,sprite_color};
    //wire [4:0]  mix_color  = 'd0;
    //wire spri_hit = (color!='d0)&(sprite_color!='d0);
    wire spri_hit = (color!='d0)&~(spr_nocolor);

    /////////////
    //wire
    wire ppu_addr_inc32 = ppu_ctrl[2];
    wire bg_addr = ppu_ctrl[4];
    wire sp_addr = ppu_ctrl[3];
    wire bg_display = ppu_mask[3];
    wire sp_display = ppu_mask[4];
    wire ppu_gen_nmi = ppu_ctrl[7];

    /////////////////////////////
    //定义一些宏
`define PPU_CPU_WDATA(dat)  \
    cpu_out_dat <= dat;
`define PPU_WDATA(dat)  \
    ppu_rd <= 'b1; ppu_rw <= 'b0; ppu_out_dat<=dat; \
    ppu_addr <= V;

`define PPU_RDATA(addr) \
    ppu_addr <= addr; ppu_rd <= 'b0; /*ppu_rw <= 'b1;*/
`define PPU_CPU_READ \
    ppu_rd <= 'b0; /* ppu_rw <= 'b1; */ \
    ppu_addr <= V;
`define PPU_READ_STATUS_PROC \
    vblank<='b0;            \
    w <= 'd0;               \
    sprite_0_hit <= 'd0;\
    sprite_line_over <= 'd0; \
    ppu_nmi <= 'b1;

    ///////////////////////////////////////////////////////////////////////////
    //处理task
    task task_ppu_rst; begin
        ppu_ctrl <= 8'h10;
        ppu_mask <= 8'hff;
        hcounter <= 'd0;
        vcounter <= 'd0;
        ppu_cs_old <= 'd1;
        ppu_nmi <= 'b1;
        T <= 'h0000;
        fine_x <= 'd6;
        `INIT_PAL
        `INIT_OAM
    end endtask;

    task task_ppu_ctrl;
    begin
        if(~cpu_rw)begin
            ppu_ctrl <= cpu_dat;        //ppu控制器
            T[11:10] <= cpu_dat[1:0];   //nametable in $000,$400,$800,$c00
        end
    end endtask

    task task_ppu_mask;
    begin
        if(~cpu_rw) ppu_mask <= cpu_dat;        //ppu的数据
    end endtask

    task task_ppu_statues;
    begin
        if(cpu_rw)begin `PPU_CPU_WDATA(ppu_status); `PPU_READ_STATUS_PROC /*w需要初始化*/ end
    end endtask

    task task_ppu_oamaddr;begin
        if(~cpu_rw) oam_addr <= cpu_dat;
    end endtask

    task task_ppu_oamdata;begin
        if(~cpu_rw)begin
            oam[oam_addr]<=cpu_dat;  oam_addr<=oam_addr+8'd1;
        end
    end endtask

    reg [15:0] scroll_w = 'd0; /*synthesis keep=1 */
    reg scroll_clk = 'd0;      /*synthesis keep=1 */

    task task_ppu_scroll;begin
        if(~cpu_rw)begin
            // if(cpu_dat=='d0)begin
            //     if(~w)begin T[4:0]<='d4;   fine_x<= 'd0;   scroll_w[7:0] <=cpu_dat; end //第一次写入
            //     else begin  T[9:5]<='d4; T[14:12]<= 'd0;   scroll_w[15:8]<=cpu_dat; end
            // end
            // else begin
                if(~w)begin T[4:0]<=cpu_dat[7:3] ;fine_x<= cpu_dat[2:0];   scroll_w[7:0] <=cpu_dat; end //第一次写入
                else begin  T[9:5]<=cpu_dat[7:3]; T[14:12]<= cpu_dat[2:0]; scroll_w[15:8]<=cpu_dat; end
            //end
            scroll_clk <= ~scroll_clk;
            ppu_ctrl[1:0] = 'd0;
            w <= ~w;
        end
    end endtask

    task task_ppu_addr;begin
        if(~cpu_rw)begin
            if(~w)begin T[15:8] <= {2'b00,cpu_dat[5:0]};     end //第一次写入
            else begin  T[7:0] <= cpu_dat;V <= {T[15:8],cpu_dat}; end //第二次写入
            w <= ~w;
        end
    end endtask

    task task_ppu_data;begin
        if(~cpu_rw)begin if(V[13:8]==6'h3F)begin
            pal_ram[V[4:0]] <= cpu_dat;
        end
        else begin
            $display("%4X-%02X,%d",V,cpu_dat,bg_display|sp_display);
            `PPU_WDATA(cpu_dat); //CPU写入
            end
        end
        else
        begin
            if(V[13:8]==6'h3f)begin
                `PPU_CPU_WDATA(pal_ram[V[4:0]]);
            end
            else
            begin
                `PPU_CPU_READ;
                `PPU_CPU_WDATA(last_ppu_dat);
            end
        end       //CPU读取
        V <= V + (ppu_addr_inc32?16'h20:16'd1);
    end endtask
    ////////////////////////////////
    //渲染的task
    reg [7:0] nt_dat = 8'd0;
    reg [7:0] at_dat = 8'd0;
    reg [15:0] tileH = 16'd0;
    reg [15:0] tileL = 16'd0;
    reg [7:0]  tileL_buf = 8'd0;
`define TILE_SHIFT \
    tileH <= {1'b1,tileH[15:1]}; \
    tileL <= {1'b1,tileL[15:1]};
`define TILE_SET(lbit,mbit) \
        tileH[15:8] <= {mbit[0],mbit[1],mbit[2],mbit[3],mbit[4],mbit[5],mbit[6],mbit[7]}; \
        tileL[15:8] <= {lbit[0],lbit[1],lbit[2],lbit[3],lbit[4],lbit[5],lbit[6],lbit[7]};

`define V_HORI_INC \
    {V[10],V[4:0]} <= {V[10],V[4:0]} + 6'd1;
    // if(V[4:0]=='h1f)begin V[10]<=~V[10]; V[4:0] <= 5'd0; end \
    // else V[4:0] <= V[4:0] + 5'd1;

`define RENDER_READ_INIT \
    render_read_nt <= 'b1; render_read_at <= 'b1; render_read_lb <= 'b1; render_read_mb <= 'b1;

`define RENDER_READ_CLEAR \
    render_read_nt <= 'b0; render_read_at <= 'b0; render_read_lb <= 'b0; render_read_mb <= 'b0;

    reg render_read_nt = 'd0;
    reg render_read_at = 'd0;
    reg render_read_lb = 'd0;       //lbit
    reg render_read_mb = 'd0;       //mbit
    reg render_read_set_addr = 'd0;

    reg [8:0]   at_bitH = 9'd0;
    reg [8:0]   at_bitL = 9'd0;
`define set_at_bit(l,h) at_bitH[8] <= h; at_bitL[8] <= l;
`define AT_BIT_SHIFT \
         at_bitH <= {at_bitH[8],at_bitH[8:1]}; \
         at_bitL <= {at_bitL[8],at_bitL[8:1]};

    /////////////////////
    //线处理
    //地址处理
    wire [13:0] nt_addr = {2'b10,V[11:0]};
    wire [13:0] at_addr = 14'h23C0 | {6'h00,V[11:10],V[9:7],V[4:2]};   //;
    wire [13:0] lb_addr = {1'b0,bg_addr,nt_dat,1'b0,V[14:12]};
    wire [13:0] mb_addr = {1'b0,bg_addr,nt_dat,1'b1,V[14:12]};

    reg  last_V1 = 'd0;
    wire [3:0] at_att_datH = V[6]?at_dat[7:4]:at_dat[3:0];
    wire [1:0] at_att_datL = ~last_V1?at_att_datH[1:0]:at_att_datH[3:2];        //需要修改,因为V提前处理了
    wire at_bitH8 = at_att_datL[1];
    wire at_bitL8 = at_att_datL[0];

    wire [3:0] color =
        (fine_x==3'd0)?{at_bitH[0],at_bitL[0],tileH[0],tileL[0]}:          //因为pat_datH在赋值的时候.已经高低位翻转
        (fine_x==3'd1)?{at_bitH[1],at_bitL[1],tileH[1],tileL[1]}:
        (fine_x==3'd2)?{at_bitH[2],at_bitL[2],tileH[2],tileL[2]}:
        (fine_x==3'd3)?{at_bitH[3],at_bitL[3],tileH[3],tileL[3]}:
        (fine_x==3'd4)?{at_bitH[4],at_bitL[4],tileH[4],tileL[4]}:
        (fine_x==3'd5)?{at_bitH[5],at_bitL[5],tileH[5],tileL[5]}:
        (fine_x==3'd6)?{at_bitH[6],at_bitL[6],tileH[6],tileL[6]}:
        (fine_x==3'd7)?{at_bitH[7],at_bitL[7],tileH[7],tileL[7]}:4'd0;
    ////////////////////////////////////
    //有关背景层的渲染
    task task_ppu_render_tile; begin
        //处理渲染
        if(bg_display)begin
            //必须开启则ppu开始读取状态
            render_read_set_addr <= ~render_read_set_addr;      //每两个像素读取一个数据
            if(vcounter<9'd240)begin
                //正在渲染
                    if((hcounter==9'd319)|(hcounter==`PPU_H_END))begin `RENDER_READ_INIT ; render_read_set_addr <= 1'b1;  end
                else if(hcounter==9'd320|(hcounter==0))begin tile_shift <= 'd1; end
                else if((hcounter=='d336)|(hcounter=='d256)) begin `RENDER_READ_CLEAR; tile_shift <= 'd0; end
                else if(hcounter=='d258)begin
                    V[4:0]<= T[4:0];
                    V[10] <= T[10];
                end
                else if(hcounter=='d257)begin
                    //处理V寄存器.下一行
                    if(V[14:12]!='d7)begin V[14:12] <= V[14:12] + 1;end
                    else begin
                        V[14:12] <= 3'd0;
                        if(V[9:5]==5'd29)begin V[9:5] <= 5'd0;V[11] <= ~V[11];     /*因为是240行扫描线 */ end
                        else begin V[9:5] <= V[9:5] + 5'd1; end
                    end
                end
                else ;
            end
            else if(vcounter==`PPU_V_END)begin
                //初始化准备
                if((hcounter==9'd319)|(hcounter==`PPU_H_END))begin `RENDER_READ_INIT;  render_read_set_addr <= 1'b1;   end
                else if(hcounter==9'd320|(hcounter==0))begin tile_shift <= 'd1; end
                else if((hcounter=='d336))begin `RENDER_READ_CLEAR; tile_shift <= 'd0; end
            end
            else if(vcounter==9'd241&&hcounter==9'd1)begin end        //设置中断
            else begin end
        end
        else begin `RENDER_READ_CLEAR; end


        if(tile_shift)begin
            `AT_BIT_SHIFT;      //属性位移
            `TILE_SHIFT;        //像素位移
        end

        if(render_read_set_addr)begin
        //第一个周期,只是设置读取地址
                 if(render_read_nt)begin `PPU_RDATA(nt_addr); if(hcounter!='d0)begin `TILE_SET(tileL_buf,last_ppu_dat); end end        // $2000~$3fff
            else if(render_read_at)begin `PPU_RDATA(at_addr); end
            else if(render_read_lb)begin `PPU_RDATA(lb_addr); end
            else if(render_read_mb)begin `PPU_RDATA(mb_addr); end         //在第二个周期时,V会递增1
        end
        else begin
            //第二个周期,这个时候rd应该是低电平
                if(render_read_nt) begin render_read_nt<='b0;  nt_dat <= ppu_dat;                  end
            else if(render_read_at)begin render_read_at<='b0;  at_dat <= ppu_dat; last_V1 <= V[1];  end
            else if(render_read_lb)begin render_read_lb<='b0;  tileL_buf <= ppu_dat;                end
            else if(render_read_mb)begin render_read_mb<='b0;  `V_HORI_INC `set_at_bit(at_bitL8,at_bitH8); `RENDER_READ_INIT; end         //在最后一个数据时,V会递增1
        end
        //$write("%02d",out_pixel);
    end endtask

    ////////////////////////////////////
    //有关精灵层的渲染
    reg sprite_clear;
    reg[4:0] OAMSec_addr = 'd0;
    reg[7:0] OAMSec_dat['h20:0];        //次内存
    reg sprite_eval = 'd0;
    reg sprite_over = 'd0;
    reg sprite_line_over = 'd0;
    reg sprite_0_hit = 'd0;
    reg sprite_0_hit_frame = 'd0;
    reg sprite_read_nt = 'd0;
    reg sprite_read_at = 'd0;
    reg sprite_read_lt = 'd0;
    reg sprite_read_mb = 'd0;
    reg sprite_read_status = 'd0;

    //计算精灵的状态
    wire       sprite_isV   = OAMSec_dat[OAMSec_addr+2][7];
    wire       sprite_isH   = OAMSec_dat[OAMSec_addr+2][6];
    wire[2:0]  sprite_eavlY = vcounter - OAMSec_dat[OAMSec_addr];           //0~7
    wire[13:0] spriteL_addr = {1'b0,sp_addr,OAMSec_dat[OAMSec_addr+1],1'b0,sprite_isV?~sprite_eavlY:sprite_eavlY};
    wire[13:0] spriteH_addr = {1'b0,sp_addr,OAMSec_dat[OAMSec_addr+1],1'b1,sprite_isV?~sprite_eavlY:sprite_eavlY};       //+8

    wire[7:0]  sprite_eval_Ypos = oam[oam_addr];
    wire[8:0]  sprite_eval_Yoffset = //vcounter>=oam[oam_addr]&&vcounter<(oam[oam_addr]+8'd8);
        {1'b0,vcounter[7:0]} - oam[oam_addr];
    wire       sprite_eval_isadd = sprite_eval_Yoffset[8:3]=='d0;

    wire[31:0] OAMSec_test1 = {OAMSec_dat[0],OAMSec_dat[1],OAMSec_dat[2],OAMSec_dat[3]};
    wire[31:0] OAMSec_test2 = {OAMSec_dat[4],OAMSec_dat[5],OAMSec_dat[6],OAMSec_dat[7]};
`define TEST32_VAL8(dat,base) \
    dat[0+(base)],dat[1+(base)],dat[2+(base)],dat[3+(base)]
`define TEST64_VAL8(dat,base) \
    TEST32_VAL8(dat,(base)),TEST32_VAL8(dat,(base)+4)

    wire[31:0] spriteX_test1 = {`TEST32_VAL8(spriteX,0)};
    wire[31:0] spriteX_test2 = {`TEST32_VAL8(spriteX,4)};
    wire[31:0] sprite_tile_test1 = {`TEST32_VAL8(sprite_tileL,0)};

    ////////
    //sprite渲染数据
    wire[2:0]sprite_pos = OAMSec_addr[4:2];
    reg[7:0] spriteX['d7:'d0];          //精灵的X计数器.如果是0.则开始渲染
    reg[7:0] sprite_tileH['d7:'d0];     //
    reg[7:0] sprite_tileL['d7:'d0];     //
    reg[1:0] sprite_pal['d7:'d0];
    reg      sprite_prio['d7:'d0];
    reg      sprite_read_end='d1;
    reg      sprite_render = 'd0;

`define SPRITE_CUR_SHIFT \
    sprite_cur_draw_tileL <= {1'b0,sprite_cur_draw_tileL[7:1]}; \
    sprite_cur_draw_tileH <= {1'b0,sprite_cur_draw_tileH[7:1]};
    reg[7:0] sprite_cur_draw_tileL;             //当前绘制的oam tileL数据
    reg[7:0] sprite_cur_draw_tileH;             //当前绘制的oam tileH数据
    reg[1:0] sprite_cur_draw_pal;               //当前绘制的调色板
    reg      sprite_cur_draw_prio = 'b1;         //当前绘制的优先级
    integer i;

    wire [3:0] sprite_color = {sprite_cur_draw_pal /*2bit*/,sprite_cur_draw_tileH[0],sprite_cur_draw_tileL[0]};


`define SPRITE_READ_INIT \
    sprite_read_nt <= 'b1; \
    sprite_read_at <= 'b1; \
    sprite_read_lt <= 'b1; \
    sprite_read_mb <= 'b1; \
    sprite_read_status <= 'b0; \
    sprite_read_end <= 'b0;

`define SPRITE_READ_CLEAR \
    sprite_read_nt <= 'b0; \
    sprite_read_at <= 'b0; \
    sprite_read_lt <= 'b0; \
    sprite_read_mb <= 'b0;

`define OAMEVAL_START sprite_eval <= 'b1; oam_addr <= 'd0; sprite_over <= 'b0;
`define DAT8_FLIP(dat) \
    ({dat[0],dat[1],dat[2],dat[3], \
    dat[4],dat[5],dat[6],dat[7]})

    task task_ppu_render_oamSec_add; begin
        OAMSec_dat[OAMSec_addr]<=oam[oam_addr];{sprite_over,OAMSec_addr} <= {1'b0,OAMSec_addr} + 7'd1; {sprite_eval,oam_addr} <= {1'b1,oam_addr} + 9'd1;
    end endtask

    task task_ppu_render_sprite;begin
        //精灵层
        if((vcounter<'d240)&(hcounter=='d0))                begin sprite_clear<=sp_display; end        //第一个像素时,开始清理OAM的次内存
        if((vcounter<'d240)&(hcounter=='d256)&sp_display)   begin `SPRITE_READ_INIT;OAMSec_addr <= 5'd0; end
        if((vcounter<'d240)&(hcounter<'d255))               begin sprite_render<=sp_display; /*如果mask[4]为0,则禁止显示精灵*/ end
        else sprite_render <= 'd0;      //精灵不显示

        if(sprite_clear)begin
            if(OAMSec_addr==5'h1f)begin sprite_clear <= 'b0; `OAMEVAL_START; end
            OAMSec_dat[OAMSec_addr] <= 8'hff;       //初始化次内存
            OAMSec_addr <= OAMSec_addr + 5'd1;
        end

        //开始计算精灵的坐标
        if(sprite_eval)begin
            /*
            如果OAMSec_addr&3!=0,则表示当前oam命中.正在复制oam到次内存
            */
            if(~sprite_over)begin   //需要sprite没有over
                if(OAMSec_addr[1:0]!=2'd0)begin task_ppu_render_oamSec_add; end
                else begin
                    if(sprite_eval_isadd)begin task_ppu_render_oamSec_add; end
                    else begin {sprite_eval,oam_addr} <= {1'b1,oam_addr} + 9'd4; /*如果oam_addr超过'hff,则sprite_eval会被置为'0,*/  end
                end
            end
        end

        if(sprite_over)begin
            sprite_line_over <= sprite_over;
        end

        if(~sprite_read_end)begin
            //开始读取sprite的数据
            if(~sprite_read_status)begin
                //设置地址
                //第一次读取
                    if(sprite_read_nt)begin end
                else if(sprite_read_at)begin end
                else if(sprite_read_lt)begin `PPU_RDATA(spriteL_addr); end
                else if(sprite_read_mb)begin `PPU_RDATA(spriteH_addr); end
            end else begin
                //读取数据
                    if(sprite_read_nt)begin  sprite_read_nt<='b0; sprite_pal[sprite_pos]  <= OAMSec_dat[OAMSec_addr+2][1:0]; sprite_prio[sprite_pos] <= OAMSec_dat[OAMSec_addr+2][5]; end
                else if(sprite_read_at)begin sprite_read_at<='b0; spriteX[sprite_pos]     <= OAMSec_dat[OAMSec_addr+3]; end
                else if(sprite_read_lt)begin sprite_read_lt<='b0; sprite_tileL[sprite_pos] <= ~sprite_isH?`DAT8_FLIP(ppu_dat):ppu_dat; end
                else if(sprite_read_mb)begin `SPRITE_READ_INIT;   sprite_tileH[sprite_pos] <= ~sprite_isH?`DAT8_FLIP(ppu_dat):ppu_dat; {sprite_read_end,OAMSec_addr}<={1'b0,OAMSec_addr}+7'd4;end //定位到下一个精灵
            end
            sprite_read_status <= ~sprite_read_status;
        end

        if(sprite_render)begin
            `SPRITE_CUR_SHIFT;      //当前tile需要位移
            for (i = 0; i<8; i=i+1) begin
                spriteX[i] <= spriteX[i] - 8'd1; //精灵位置减一
                if(spriteX[i]==8'd0)begin
                    //复制到当前复制的数据
                    //优先级是从0到7
                    //需要处理sprite的优先级
                    sprite_cur_draw_tileH <= sprite_cur_draw_tileH | sprite_tileH[i];
                    sprite_cur_draw_tileL <= sprite_cur_draw_tileL | sprite_tileL[i];
                    sprite_cur_draw_pal <=   sprite_pal[i];
                end
            end
        end
    end endtask;

    ////////////////////////////////////////////////////////////////////////////
    always @(negedge ppu_clk) begin
    end

    always @(negedge ppu_clk or negedge rstn) begin
        if(~rstn)begin task_ppu_rst; end
        else begin
            if(~ppu_rd)begin
                last_ppu_dat <= ppu_dat;
                ppu_rd <= ~ppu_rd;
            end
            //计数器
            if(hcounter==`PPU_H_END)begin
                hcounter <= 9'd0;
                if(vcounter==`PPU_V_END)begin vcounter <= 9'd0; end
                else begin vcounter <= vcounter + 9'd1; end
            end else hcounter <= hcounter + 9'd1;

            if(hcounter==0&&vcounter==241)begin
                vblank <= 'b1;
                ppu_nmi <= ~ppu_gen_nmi;
                oam_addr <= 'd0;
            end
            else if(hcounter==256&&vcounter==`PPU_V_END)begin
                ppu_nmi <= 'b1;
                sprite_0_hit_frame <= 'b0;
                sprite_0_hit <= 'b0;
            end
            else if(hcounter=='d0&&vcounter=='d0)begin
                $display("bg:%d,%d",bg_display,sp_display);
            end
            // else if(hcounter=='d250&&vcounter=='d255)begin
            //     ppu_addr <= 'h2000;
            //     ppu_out_dat <= 'h02;
            //     ppu_rw <= 'b0;
            //     ppu_rd <= 'b1;
            // end
            //开始处理

            if(ppu_cs_old!=ppu_cs)begin
                //因为nes的1.7MHz的时序比ppu像素5.8MHz快.只需要一次读写
                ppu_cs_old <= ppu_cs;
                if(~ppu_cs)begin
                    case (cpu_addr)
                    `PPU_CTRL    :task_ppu_ctrl    ();
                    `PPU_MASK    :task_ppu_mask    ();
                    `PPU_STATUES :task_ppu_statues ();
                    `PPU_OAMADDR :task_ppu_oamaddr ();
                    `PPU_OAMDATA :task_ppu_oamdata ();
                    `PPU_SCROLL  :task_ppu_scroll  ();
                    `PPU_ADDR    :task_ppu_addr    ();
                    `PPU_DATA    :task_ppu_data    ();
                    default:;
                    endcase
                end
            end

            //ppu_ctrl <= 'h10;

            //开始绘制
            //时序参考https://www.nesdev.org/w/images/default/4/4f/Ppu.svg
            task_ppu_render_tile;

            task_ppu_render_sprite;

            if(spri_hit&~sprite_0_hit_frame)begin
                sprite_0_hit_frame <= 'b1;
                sprite_0_hit <= 'b1;
                $display("sprite hit:%d,%d",out_y,sprite_0_hit_frame);
            end

            if(cpu_rw&~ppu_rw)begin
                ppu_rw <= 'b1;
            end
        end
    end
endmodule

/*
有关一些注意事项
tileH,tileL
    是在x==320时,被赋值为0. 但其实是在329的时候被赋值lbit与mbit,
    第327的时候停止位移.
    第2的时候继续开始位移

at_dat获得的数据原本是在第21个像素
    本源码是在第20个像素.(提前一个像素获得数据并且设置at_datL,datH.暂时没发现有啥bug)
        注,21,20只是一个位置比较例子

未实现处理精灵与背景层的优先级

*/
